"""A set of data wrappers and query interfaces to let you choose BGG games

This is an appengine application that will eventually support all sorts of
JS stuff, and multiple users.
"""
import logging
import os
import csv
import re
import urllib
import StringIO

from django.utils import simplejson as json
from google.appengine.api import urlfetch
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.runtime import apiproxy_errors

from google.appengine.ext import db

# application specific imports.
import data
import util

class MainPage(webapp.RequestHandler):
  def get(self):
    user = users.get_current_user()

    if not user:
      self.redirect(users.create_login_url(self.request.uri))
      return

    self.response.headers['Content-Type'] = 'text/html'

    # get preferences.
    pref_filter = data.AccountPreference.all()
    pref_filter.filter("account =", user)
    pref = pref_filter.fetch(1)
    if not pref:
      pref = data.AccountPreference()
      pref.account = user
      pref.put()
      user_list = []
    else:
      pref = pref[0]
      user_list = pref.displayed_users

    user_list.sort()

    user_control = StringIO.StringIO()
    print >>user_control, '<select name="bgguserlist" id="bgguserlist" onchange="ClearNameText(); UpdateFrame();">'
    print >>user_control, '<option value="" selected="selected"></option>'
    for bgguser in user_list:
      print >>user_control, '<option value="%s">%s</option>' % (bgguser,
                                                                bgguser)
    print >>user_control, '</select>'

    # template values for the main page.
    values = { "user_control" : user_control.getvalue() }

    template_path = os.path.join(os.path.dirname(__file__),
                                 "templates/mainpage.html")
    self.response.out.write(template.render(template_path, values))


class MainPageJson(webapp.RequestHandler):
  def get(self):
    user = users.get_current_user()

    if not user:
      self.redirect(users.create_login_url(self.request.uri))
      return

    self.response.headers['Content-Type'] = 'text/html'

    # get preferences.
    pref_filter = data.AccountPreference.all()
    pref_filter.filter("account =", user)
    pref = pref_filter.fetch(1)
    if not pref:
      pref = data.AccountPreference()
      pref.account = user
      pref.put()
      user_list = []
    else:
      pref = pref[0]
      user_list = pref.displayed_users

    user_list.sort()

    user_control = StringIO.StringIO()
    print >>user_control, '<select name="bgguserlist" id="bgguserlist" onchange="ClearNameText(); UpdateFrame();">'
    print >>user_control, '<option value="" selected="selected"></option>'
    for bgguser in user_list:
      print >>user_control, '<option value="%s">%s</option>' % (bgguser,
                                                                bgguser)
    print >>user_control, '</select>'

    # template values for the main page.
    values = { "user_control" : user_control.getvalue() }

    template_path = os.path.join(os.path.dirname(__file__),
                                 "templates/mainpage-json.html")
    self.response.out.write(template.render(template_path, values))


class ChooseGames(webapp.RequestHandler):
  def get(self):
    "Handler for the game selection request. This function is mostly glue"
    # we won't autoredirect, so folks can try this themselves.
    response = self.BuildChunkResponse()

    if self.request.get("output") == "json":
      self.RenderJSON(response)
    else:
      self.RenderHTML(response)

  def RenderHTML(self, response):
    "Renders an HTML (or text) version of a response"

    if "error" in response:
      self.response.headers["Content-Type"] = "text/plain"
      self.response.out.write(response["error"])
      return

    if "games" in response:
      self.response.headers["Content-Type"] = "text/plain"

      # Replace with a general query output:
      #self.response.out.write("Best with: %d\n",
      #                        response["query"]["best_with"])
      self.response.out.write(
        "%-5s %-5s %-5s %s\n" % ("bgg ", "user", "time", "name"))

      for g in response["games"]:
        self.response.out.write(
          "%-5s %-5s %-5s %s\n" % (g["bgg_rating"], g["user_rating"],
                                   g["play_time"], g["name"]))

  def RenderJSON(self, response):
    "Returns a response object in JSON format"

    if "games" in response and self.request.get("full", "dummy"):
      # we haven't been passed the full parameter, truncate and note.
      response["games"] = response["games"][0:50]

    self.response.headers["Content-Type"] = "application/json"
    self.response.out.write(json.dumps(response))

  def BuildChunkResponse(self):
    "Creates a response dict containing errors, or the list of objects"

    user = users.get_current_user()
    response = {}

    if not user:
      response["error"] = "Unauthenticated access"
      return response

    # TODO: templatise all this stuff

    # Maybe check whether to trigger a fetch and wait if the user is
    # too stale.
    bgguser = self.request.get('bgguser').lower().strip()
    if not bgguser:
      response["error"] = "No bgguser specified"
      return response

    # TODO: check whether we already have this in memcache, fetch + store
    # if not.

    # check that we have some data for this user by fetching it.
    coll_query = data.GameChunk.all()
    coll_query.filter("bgguser =", bgguser)
    res = coll_query.fetch(1)
    if not res:
      # TODO: redirect this to the fetch interface.
      try:
        util.AddMissingRecord(bgguser)
        response["error"] = ("Choosegame has no data for the selected user. "
                             "We will attempt to load the user data, "
                             "please try again in 5 minutes.")
        response["errorcode"] = "MISSINGUSER"
      except apiproxy_errors.CapabilityDisabledError, message:
        logging.error(message)
        response["error"] = ("Choosegame has no data for the selected user "
                             "and we are unable to load it at this time. "
                             "Please try again in half an hour")
        response["errorcode"] = "MISSINGUSER,NOLOAD"
      return response
    chunkdata = res[0]

    # update preferences to record the last fetch done by this user.
    # This isn't critical, if we fail in some way, keep going as best we can.
    if user:
      user_prefs_query = data.AccountPreference.all()
      user_prefs_query.filter("account =", user)
      user_prefs_list = user_prefs_query.fetch(1)

      if user_prefs_list:
        user_prefs = user_prefs_list[0]
      else:
        user_prefs = data.AccountPreference()
        user_prefs.account = user

      if not bgguser in user_prefs.displayed_users:
        user_prefs.displayed_users.append(bgguser)
      user_prefs.choose_count += 1
      try:
        user_prefs.put()
      except apiproxy_errors.OverQuotaError, message:
        logging.error(message)
      except apiproxy_errors.CapabilityDisabledError, message:
        logging.error(message)

    # We've already got the games in a helpful form in our chunk. Let's work
    # with that.

    if len(chunkdata.csvdata) == 0:
      response["error"] = "There are no BGG game records for the selected user, or the selected user is not a BGG member"
      response["errorcode"] = "NORECORDS"
      return response

    gamecsv = csv.reader(chunkdata.csvdata.encode('utf-8').split("\n"))
    headings = None
    index = {}


    # Set up all the filters we're going to be using to select games based
    # on the input search parmeters.

    # Figure out how to input and select on ranges for these.
    best_with = util.SafeInt(self.request.get("bestwith"))
    if best_with:
      best_with_filter = ":%d:" % best_with

    rec_with = util.SafeInt(self.request.get("recwith"))
    if rec_with:
      rec_with_filter = ":%d:" % rec_with

    filter_time = util.SafeInt(self.request.get("time"))
    if filter_time:
      time_min = int(filter_time * 2 / 3)
      time_max = int(filter_time * 4 / 3)

    unranked = self.request.get("unranked", "no")
    if unranked == "no":
      unranked = False
    else:
      unranked = True

    weight_delta = 0.75
    weight = util.SafeFloat(self.request.get("weight"), default=-1.0)
    if weight > 0:
      weight_min = weight - weight_delta
      weight_max = weight + weight_delta

    self.response.headers["Content-Type"] = "text/plain"
    response["games"] = []
    games_seen = set()

    for row in gamecsv:
      if not headings:
        headings = row
        continue

      rowd = dict(zip(headings, row))
      if len(rowd) < 5:
        continue

      if 0:
        if not "error" in response:
          response["error"] = ""
          response["count"] = 0

        response["error"] += str(rowd) + "XXX"
        response["count"] += 1

        if response["count"] > 5:
          return response


      # apply filters.

      # We currently can't distinguishe unranked games from other
      # expansions, so we have this option, default no.
      if (not unranked) and ("rank" in rowd) and (rowd["rank"] == "0"):
        continue

      if best_with:
        if not best_with_filter in rowd["best_with"]:
          continue
      if rec_with:
        if not rec_with_filter in rowd["rec_with"]:
          continue
      if weight > 0.0:
        game_weight = util.SafeFloat(rowd["weight"], 0.0)
        if game_weight < weight_min or game_weight > weight_max:
          continue
      if filter_time:
        game_time = util.SafeInt(rowd["playing_time"])
        if game_time < time_min or game_time > time_max:
          continue

      # TODO: check if this is actually necessary in chunk land
      if rowd["id"] in games_seen:
        continue
      games_seen.add(rowd["id"])

      # if filter_time:
      #   plaand (
      #   rowd.playing_time < time_min or g.playing_time > time_max):
      #     continue

      # if filter_weight and (
      #   g.weight < weight_min or g.weight > weight_max):
      #     continue

      # massage data for return.
      my_rating = util.SafeFloat(rowd["rating"], default=-1.0)
      if my_rating < 0.1:
        my_rating = ""
      else:
        my_rating = "%.1f" % my_rating

      # figure out a nicer way to get ratings to sort.
      response["games"].append({
        "best_with" : rowd["best_with"].strip(":").split(":"),
        "rec_with" : rowd["rec_with"].strip(":").split(":"),
        "bgg_rating": util.SafeFloat(rowd["avg_rating"], default=1.0),
        "user_rating": my_rating,
        "play_time": rowd["playing_time"],
        "id" : rowd["id"],
        "name" : unicode(rowd["name"], 'utf-8'),

        })

    response["games"].sort(key=lambda x: x["bgg_rating"], reverse=True)

    def FixScores(x):
      x["bgg_rating"] = ("%0.1f" % x["bgg_rating"])
    map(FixScores, response["games"])

    response["num_games"] = len(response["games"])

    print response
    # response["error"] = str(response)
    return response

  def QueryGames(self):
    """All the under-the-hood details of getting the game data.

    Returns a dict with the following members
    "status" (!="ok" is a failure, with error embedded)
    "games" (an array of game dicts)

    This should be serialised for the calling output format.
    """
    retval = {}



application = webapp.WSGIApplication(
                                     [('/old', MainPage),
                                      ('/', MainPageJson),
                                      ('/choose', ChooseGames),
                                      ],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()
